<body>

<script id="draw-shader-vs" type="x-shader/x-vertex">
precision highp float;

attribute vec3 inPos;
attribute vec3 inNV;
attribute vec2 inUV;
//attribute vec3 inCol;

varying vec3  v_view_pos;
varying vec3  v_view_nv;
varying vec2  v_uv;
//varying vec3  v_col;
varying float clip_distance;

uniform mat4 u_projectionMat44;
uniform mat4 u_viewMat44;
uniform mat4 u_modelMat44;
uniform vec4 u_clipPlane;

void main()
{
    mat4 mv        = u_viewMat44 * u_modelMat44;
    //v_col          = inCol;
    v_uv           = inUV;
    v_view_nv      = normalize(mat3(mv) * inNV);
    vec4 viewPos   = mv * vec4( inPos, 1.0 );
    v_view_pos     = viewPos.xyz;
    gl_Position    = u_projectionMat44 * viewPos;
    vec4 worldPos  = u_modelMat44 * vec4( inPos, 1.0 );
    vec4 clipPlane = vec4(normalize(u_clipPlane.xyz), u_clipPlane.w);
    clip_distance  = dot(worldPos, clipPlane);
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;

varying vec3  v_view_pos;
varying vec3  v_view_nv;
varying vec2  v_uv;
//varying vec3  v_col;
varying float clip_distance;

uniform sampler2D u_texture;
uniform sampler2D u_displacement_map;
uniform vec2      u_displacement_map_size;
uniform float     u_displacement_scale;
uniform vec2      u_parallax_quality;

uniform vec3  u_lightDir;
uniform float u_ambient;
uniform float u_diffuse;
uniform float u_specular;
uniform float u_shininess;

float CalculateHeight( in vec2 texCoords )
{
    float height = texture2D( u_displacement_map, texCoords ).x;
    return clamp( height, 0.0, 1.0 );
}

//#define NORMAL_MAP_TEXTURE
#define NORMAL_MAP_QUALITY 0

vec4 CalculateNormal( in vec2 texCoords )
{
#if defined(NORMAL_MAP_TEXTURE)
    float height = CalculateHeight( texCoords );
    vec3  tempNV = texture2D( u_normal_map, texCoords ).xyz * 2.0 / 1.0;
    return vec4( normalize( tempNV ), height );
#else
    vec2 texOffs = 1.0 / u_displacement_map_size;
    vec2 scale   = 1.0 / texOffs;
#if NORMAL_MAP_QUALITY > 0
    float hx[9];
    hx[0] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2(-1.0, -1.0) ).r;
    hx[1] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 0.0, -1.0) ).r;
    hx[2] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 1.0, -1.0) ).r;
    hx[3] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2(-1.0,  0.0) ).r;
    hx[4] = texture2D( u_displacement_map, texCoords.st ).r;
    hx[5] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 1.0, 0.0) ).r;
    hx[6] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2(-1.0, 1.0) ).r;
    hx[7] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 0.0, 1.0) ).r;
    hx[8] = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 1.0, 1.0) ).r;
    vec2  deltaH = vec2(hx[0]-hx[2] + 2.0*(hx[3]-hx[5]) + hx[6]-hx[8], hx[0]-hx[6] + 2.0*(hx[1]-hx[7]) + hx[2]-hx[8]);
    float h_mid  = hx[4];
    #else
    float h_mid  = texture2D( u_displacement_map, texCoords.st ).r;
    float h_xa   = texture2D( u_displacement_map, texCoords.st + texOffs * vec2(-1.0,  0.0) ).r;
    float h_xb   = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 1.0,  0.0) ).r;
    float h_ya   = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 0.0, -1.0) ).r;
    float h_yb   = texture2D( u_displacement_map, texCoords.st + texOffs * vec2( 0.0,  1.0) ).r;
    vec2  deltaH = vec2(h_xa-h_xb, h_ya-h_yb);
#endif
    return vec4( normalize( vec3( deltaH * scale, 1.0 ) ), h_mid );
#endif
}

mat3 mat3_inverse( mat3 A )
{
    //float a = A[0][0]; float b = A[0][1]; float c = A[0][2];
    //float d = A[1][0]; float e = A[1][1]; float f = A[1][2];
    //float g = A[2][0]; float h = A[2][1]; float i = A[2][2];
    //return mat3(
    //    vec3( e*i-f*h, c*h-b*i, b*f-c*e ),
    //    vec3( f*g-d*i, a*i-c*g, c*d-a*f ),
    //    vec3( d*h-e*g, b*g-a*h, a*e-b*d ) );
    mat3 M_t = mat3(
    vec3( A[0][0], A[1][0], A[2][0] ),
    vec3( A[0][1], A[1][1], A[2][1] ),
    vec3( A[0][2], A[1][2], A[2][2] ) );
    float det = dot( cross( M_t[0], M_t[1] ), M_t[2] );
    mat3 adjugate = mat3( cross( M_t[1], M_t[2] ),
    cross( M_t[2], M_t[0] ),
    cross( M_t[0], M_t[1] ) );
    return adjugate / det;
}

void main()
{
    if ( clip_distance < 0.0 )
        discard;

    vec2  texCoords     = v_uv;    
    float face_sign     = sign(dot(v_view_nv, -v_view_pos));
    
    // Followup: Normal Mapping Without Precomputed Tangents [http://www.thetenthplanet.de/archives/1180]
    vec3  N             = normalize(v_view_nv);
    vec3  dp1           = dFdx( v_view_pos );
    vec3  dp2           = dFdy( v_view_pos );
    vec2  duv1          = dFdx( v_uv );
    vec2  duv2          = dFdy( v_uv );
    vec3  dp2perp       = cross(dp2, N);
    vec3  dp1perp       = cross(N, dp1);
    vec3  T             = dp2perp * duv1.x + dp1perp * duv2.x;
    vec3  B             = dp2perp * duv1.y + dp1perp * duv2.y;
    float invmax        = inversesqrt(max(dot(T, T), dot(B, B)));
    mat3  tbnMat        = mat3(T * invmax, B * invmax, N * u_displacement_scale);

    float parallaxScale = 0.1;
    float mapHeight     = CalculateHeight( texCoords.st );
    vec3  texDir3D      = normalize( mat3_inverse( tbnMat ) * v_view_pos );
    //vec2  texCoordOffst = parallaxScale * mapHeight * texDir3D.xy / texDir3D.z;
    vec2  texCoordOffst = parallaxScale * mapHeight * texDir3D.xy;
    vec2  newTexCoords  = texCoords.st + u_displacement_scale * texCoordOffst.xy;
    texCoords.st        = newTexCoords.xy;

    vec4 normalVec      = CalculateNormal( v_uv );
    tbnMat[2]           = face_sign * N / u_displacement_scale;
    vec3 nvMappedEs     = normalize( tbnMat * normalVec.xyz );
    
    //vec3  color         = v_col;
    vec3 color          = texture2D( u_texture, v_uv.st ).rgb;

    // ambient part
    vec3 lightCol = u_ambient * color;

    // diffuse part
    vec3  normalV = normalize( nvMappedEs );
    vec3  lightV  = normalize( -u_lightDir );
    float NdotL   = max( 0.0, dot( normalV, lightV ) );
    lightCol     += NdotL * u_diffuse * color;

    // specular part
    vec3  eyeV      = normalize( -v_view_pos );
    vec3  halfV     = normalize( eyeV + lightV );
    float NdotH     = max( 0.0, dot( normalV, halfV ) );
    float kSpecular = ( u_shininess + 2.0 ) * pow( NdotH, u_shininess ) / ( 2.0 * 3.14159265 );
    lightCol       += kSpecular * u_specular * color;

    gl_FragColor = vec4( lightCol.rgb, 1.0 );
}
</script>

<style>
html, body {
    height: 100%;
    width: 100%;
    margin: 0;
    overflow: hidden;
}

#gui {
    position: absolute;
    top: 0;
    left: 0;
    font-size : large;
}

#ref-link {
    position: absolute;
    bottom: 0;
    left: 0;
    font-size : large;
}
</style>


<div>
    <form id="gui" name="inputs">
        <table>
            <tr><td> <font color=#CCF><span id="fps_val">0</span></font> </td></tr>
            <tr>
                <td> <font color=#CCF>clipping</font> </td>
                <td> <input type="range" id="clip" min="0" max="100" value="100" onchange="changeEventHandler(event);" /></td>
            </tr>
        </table>
    </form>
</div>

    <!--div id="ref-link">
          <a href="">
          </a>
    </div-->

    <canvas id="canvas" style="border: none;"></canvas>
    <!--canvas id="canvas" style="border: none;" width="400" height="400"></canvas-->

    <script type="text/javascript">

        var readInput = true;
        function changeEventHandler(event) {
            readInput = true;
        }

        (function loadscene() {

            var gl, progDraw, vp_size, camera;
            var bufCube = {};
            var clip = 0.0;
            var tex_unit = 1;
            var height_map_unit = 2;
            var height_map_size = [0, 0];

            function render(deltaMS) {

                if (readInput) {
                    //readInput = false;
                    clip = (document.getElementById("clip").value - 50) / 50;
                    document.getElementById("fps_val").innerHTML = getFPS(deltaMS).toFixed(1);
                }

                camera.Update( vp_size );

                gl.viewport(0, 0, vp_size[0], vp_size[1]);
                gl.enable(gl.DEPTH_TEST);
                gl.clearColor(0.0, 0.0, 0.0, 1.0);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

                // set up draw shader
                ShProg.Use(progDraw);
                ShProg.SetI1(progDraw, "u_texture", tex_unit);
                ShProg.SetI1(progDraw, "u_displacement_map", height_map_unit);
                ShProg.SetF2(progDraw, "u_displacement_map_size", height_map_size);
                ShProg.SetF1(progDraw, "u_displacement_scale", 0.2);
                ShProg.SetF2(progDraw, "u_parallax_quality", [2.0, 1.0]);
                ShProg.SetF3(progDraw, "u_lightDir", [-1.0, -0.5, -2.0]);
                ShProg.SetF1(progDraw, "u_ambient", 0.2);
                ShProg.SetF1(progDraw, "u_diffuse", 0.8);
                ShProg.SetF1(progDraw, "u_specular", 0.8);
                ShProg.SetF1(progDraw, "u_shininess", 10.0);
                ShProg.SetM44(progDraw, "u_projectionMat44", camera.Perspective());
                ShProg.SetM44(progDraw, "u_viewMat44", camera.Orbit());
                var modelMat = IdentM44();
                modelMat = camera.AutoModelMatrix();
                //modelMat = RotateAxis( modelMat, 0.3, 0 );
                //modelMat = RotateAxis( modelMat, 0.8, 0 );
                //modelMat = RotateAxis( modelMat, 0.65, 1 );
                ShProg.SetM44(progDraw, "u_modelMat44", modelMat);
                ShProg.SetF4(progDraw, "u_clipPlane", [1.0, -1.0, 0.0, clip * 1.7321]);

                // draw scene
                VertexBuffer.Draw(bufCube);

                requestAnimationFrame(render);
            }

            function resize() {
                //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
                //vp_size = [canvas.width, canvas.height]
                vp_size = [window.innerWidth, window.innerHeight]
                canvas.width = vp_size[0];
                canvas.height = vp_size[1];
            }

            function initScene() {

                canvas = document.getElementById("canvas");
                gl = canvas.getContext("experimental-webgl");
                //gl = canvas.getContext( "webgl2" );
                if (!gl)
                    return null;
                var standard_derivatives = gl.getExtension("OES_standard_derivatives");  // dFdx, dFdy
                if (!standard_derivatives)
                    alert('no standard derivatives support (no dFdx, dFdy)');

                progDraw = ShProg.Create(
                    [{ source: "draw-shader-vs", stage: gl.VERTEX_SHADER },
                    { source: "draw-shader-fs", stage: gl.FRAGMENT_SHADER }
                    ]);
                if (!progDraw.progObj)
                    return null;
                progDraw.inPos = ShProg.AttrI(progDraw, "inPos");
                progDraw.inNV = ShProg.AttrI(progDraw, "inNV");
                progDraw.inUV = ShProg.AttrI(progDraw, "inUV");
                //progDraw.inCol = ShProg.AttrI(progDraw, "inCol");

                // create cube
                var cubePos = [
                    -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,
                    -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0];
                var cubeCol = [1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
                var cubeHlpInx = [0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5];
                var cubePosData = [];
                for (var i = 0; i < cubeHlpInx.length; ++i) {
                    cubePosData.push(cubePos[cubeHlpInx[i] * 3], cubePos[cubeHlpInx[i] * 3 + 1], cubePos[cubeHlpInx[i] * 3 + 2]);
                }
                var cubeNVData = [];
                for (var i1 = 0; i1 < cubeHlpInx.length; i1 += 4) {
                    var nv = [0, 0, 0];
                    for (i2 = 0; i2 < 4; ++i2) {
                        var i = i1 + i2;
                        nv[0] += cubePosData[i * 3]; nv[1] += cubePosData[i * 3 + 1]; nv[2] += cubePosData[i * 3 + 2];
                    }
                    for (i2 = 0; i2 < 4; ++i2)
                        cubeNVData.push(nv[0], nv[1], nv[2]);
                }
                var cubeColData = [];
                for (var is = 0; is < 6; ++is) {
                    for (var ip = 0; ip < 4; ++ip) {
                        cubeColData.push(cubeCol[is * 3], cubeCol[is * 3 + 1], cubeCol[is * 3 + 2]);
                    }
                }
                var cubeTexData = []
                for (var i = 0; i < 6; ++i) {
                    cubeTexData.push(0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0);
                }
                var cubeInxData = [];
                for (var i = 0; i < cubeHlpInx.length; i += 4) {
                    cubeInxData.push(i, i + 1, i + 2, i, i + 2, i + 3);
                }
                bufCube = VertexBuffer.Create(
                    [{ data: cubePosData, attrSize: 3, attrLoc: progDraw.inPos },
                    { data: cubeNVData, attrSize: 3, attrLoc: progDraw.inNV },
                    { data: cubeTexData, attrSize: 2, attrLoc: progDraw.inUV },
                    //{ data: cubeColData, attrSize: 3, attrLoc: progDraw.inCol }
                    ],
                    cubeInxData);

                Texture.LoadTexture2D(tex_unit, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/example_1_texture.png");
                Texture.LoadTexture2D(height_map_unit, "https://raw.githubusercontent.com/Rabbid76/graphics-snippets/master/resource/texture/example_1_heightmap.png");

                window.onresize = resize;
                resize();
                camera = new Camera( [0, 2.5, 0.0], [0, 0, 0], [0, 0, 1], 90, vp_size, 0.5, 100 );
                requestAnimationFrame(render);
            }

            var times     = [];
            var timeSum   = 0;
            var actIndex  = 0;
            var maxTimes  = 100;
            var first     = true;
            var prevTime  = 0;
            function getFPS(delta_sum_MS) {
                if (first)
                { 
                  prevTime = delta_sum_MS;
                  first = false;
                  return 0;
                }
                var deltaMS = delta_sum_MS - prevTime;
                prevTime = delta_sum_MS; 
                timeSum = timeSum + deltaMS;
                if ( times.length < maxTimes ) {
                    times.push(deltaMS);
                } else {
                    timeSum         = timeSum - times[actIndex];
                    times[actIndex] = deltaMS;
                    actIndex        = actIndex < maxTimes-1 ? actIndex+1 : 0;
                }
                return 1000 * times.length / timeSum;
            }

function Fract( val ) { 
    return val - Math.trunc( val );
}
function CalcAng(deltaTime, intervall) {
    return Fract(deltaTime / (1000 * intervall)) * 2.0 * Math.PI;
}
function CalcMove(deltaTime, intervall, range) {
    var pos = Fract(deltaTime / (1000 * intervall)) * 2.0
    var pos = pos < 1.0 ? pos : (2.0-pos)
    return range[0] + (range[1] - range[0]) * pos;
}    

function IdentM44() { return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; }

function RotateAxis(matA, angRad, axis) {
    var aMap = [ [1, 2], [2, 0], [0, 1] ];
    var a0 = aMap[axis][0], a1 = aMap[axis][1]; 
    var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
    var matB = matA.slice(0);
    for ( var i = 0; i < 3; ++ i ) {
        matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
        matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
    }
    return matB;
}

function Rotate(matA, angRad, axis) {
    var s = Math.sin(angRad), c = Math.cos(angRad);
    var x = axis[0], y = axis[1], z = axis[2]; 
    matB = [
        x*x*(1-c)+c,   x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0,
        y*x*(1-c)+z*s, y*y*(1-c)+c,   y*z*(1-c)-x*s, 0,
        z*x*(1-c)-y*s, z*y*(1-c)+x*s, z*z*(1-c)+c,   0,
        0,             0,             0,             1 ];
    return Multiply(matA, matB);
}    

function Multiply(matA, matB) {
    matC = IdentM44();
    for (var i0=0; i0<4; ++i0 )
        for (var i1=0; i1<4; ++i1 )
            matC[i0*4+i1] = matB[i0*4+0] * matA[0*4+i1] + matB[i0*4+1] * matA[1*4+i1] + matB[i0*4+2] * matA[2*4+i1] + matB[i0*4+3] * matA[3*4+i1]  
    return matC;
}

function Cross(a, b) { return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0]; }
function Dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
function Normalize(v) {
    var len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    return [v[0] / len, v[1] / len, v[2] / len];
}

Camera = function( pos, target, up, fov_y, vp, near, far ) {
this.Time = function() { return Date.now(); }
this.pos = pos;
this.target = target;
this.up = up;
this.fov_y = fov_y;
this.vp = vp;
this.near = near;
this.far = far;
this.orbit_mat = this.current_orbit_mat = this.model_mat = this.current_model_mat = IdentM44();
this.mouse_drag = this.auto_spin = false;
this.auto_rotate = true;
this.mouse_start = [0, 0];
this.mouse_drag_axis = [0, 0, 0];
this.mouse_drag_angle = 0;
this.mouse_drag_time = 0;
this.drag_start_T = this.rotate_start_T = this.Time();
this.Ortho = function() {
    var fn = this.far + this.near;
    var f_n = this.far - this.near;
    var w = this.vp[0];
    var h = this.vp[1];
    return [
        2/w, 0,   0,       0,
        0,   2/h, 0,       0,
        0,   0,   -2/f_n,  0,
        0,   0,   -fn/f_n, 1 ];
};  
this.Perspective = function() {
    var n = this.near;
    var f = this.far;
    var fn = f + n;
    var f_n = f - n;
    var r = this.vp[0] / this.vp[1];
    var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
    return [
        t/r, 0, 0,          0,
        0,   t, 0,          0,
        0,   0, -fn/f_n,   -1,
        0,   0, -2*f*n/f_n, 0 ];
}; 
this.LookAt = function() {
    var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
    var mx = Normalize( Cross( this.up, mz ) );
    var my = Normalize( Cross( mz, mx ) );
    var tx = Dot( mx, this.pos );
    var ty = Dot( my, this.pos );
    var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos ); 
    return [mx[0], my[0], mz[0], 0, mx[1], my[1], mz[1], 0, mx[2], my[2], mz[2], 0, tx, ty, tz, 1]; 
};
this.Orbit = function() {
    return Multiply(this.LookAt(), this.OrbitMatrix());
}; 
this.OrbitMatrix = function() {
    return (this.mouse_drag || (this.auto_rotate && this.auto_spin)) ? Multiply(this.current_orbit_mat, this.orbit_mat) : this.orbit_mat;
};
this.AutoModelMatrix = function() {
    return this.auto_rotate ? Multiply(this.current_model_mat, this.model_mat) : this.model_mat;
};
this.Update = function(vp_size) {
    if (vp_size)
        this.vp = vp_size;
    var current_T = this.Time();
    this.current_model_mat = IdentM44()
    if (this.mouse_drag) {
        this.current_orbit_mat = Rotate(IdentM44(), this.mouse_drag_angle, this.mouse_drag_axis);
    } else if (this.auto_rotate) {
        if (this.auto_spin ) {
            if (this.mouse_drag_time > 0 ) {
                var angle = this.mouse_drag_angle * (current_T - this.rotate_start_T) / this.mouse_drag_time;
                this.current_orbit_mat = Rotate(IdentM44(), angle, this.mouse_drag_axis);
            }
        } else {
            var auto_angle_x = Fract( (current_T - this.rotate_start_T) / 13000.0 ) * 2.0 * Math.PI;
            var auto_angle_y = Fract( (current_T - this.rotate_start_T) / 17000.0 ) * 2.0 * Math.PI;
            this.current_model_mat = RotateAxis( this.current_model_mat, auto_angle_x, 0 );
            this.current_model_mat = RotateAxis( this.current_model_mat, auto_angle_y, 1 );
        }
    }
};
this.ChangeMotionMode = function(drag, spin, auto ) {
    var new_drag = drag;
    var new_auto = new_drag ? false : auto;
    var new_spin = new_auto ? spin : false;
    change = this.mouse_drag != new_drag || this.auto_rotate != new_auto || this.auto_spin != new_spin; 
    if (!change)
        return;
    if (new_drag && !this.mouse_drag) {
        this.drag_start_T = this.Time();
        this.mouse_drag_angle = 0.0;
        this.mouse_drag_time = 0;
    }
    if (new_auto && !this.auto_rotate)
        this.rotate_start_T = this.Time();
    this.mouse_drag = new_drag; 
    this.auto_rotate = new_auto;  
    this.auto_spin = new_spin;
    this.orbit_mat = Multiply(this.current_orbit_mat, this.orbit_mat);
    this.current_orbit_mat = IdentM44();
    this.model_mat = Multiply(this.current_model_mat, this.model_mat);
    this.current_model_mat = IdentM44();
};
this.OnMouseDown = function( event ) {
    var rect = gl.canvas.getBoundingClientRect();
    if ( event.clientX < rect.left || event.clientX > rect.right ) return;
    if ( event.clientY < rect.top || event.clientY > rect.bottom ) return;
    if (event.button == 0) { // left button
        this.mouse_start = [event.clientX, event.clientY]; 
        this.ChangeMotionMode( true, false, false );
    }
};
this.OnMouseUp = function( event ) {
    if (event.button == 0) { // left button
        this.ChangeMotionMode( false, true, true );
    } else if (event.button == 1) {// middle button
        this.ChangeMotionMode( false, false, !this.auto_rotate );
    }
};
this.OnMouseMove = function( event ) {
    var dx = (event.clientX-this.mouse_start[0]) / this.vp[0];
    var dy = (event.clientY-this.mouse_start[1]) / this.vp[1];
    var len = Math.sqrt(dx*dx + dy*dy);
    if (this.mouse_drag && len > 0) {
        this.mouse_drag_angle = Math.PI*len;
        this.mouse_drag_axis = [dy/len, 0, -dx/len];
        this.mouse_drag_time = this.Time() - this.drag_start_T;
    }
};

this.domElement = document;
var cam = this;
//this.domElement.addEventListener( 'contextmenu', function(e) { event.preventDefault(); }, false );
this.domElement.addEventListener( 'mousedown', function(e) { cam.OnMouseDown(e) }, false );
this.domElement.addEventListener( 'mouseup', function(e) { cam.OnMouseUp(e) }, false );
this.domElement.addEventListener( 'mousemove', function(e) { cam.OnMouseMove(e) }, false );
//this.domElement.addEventListener( 'mousewheel', hid_events.onMouseWheel, false );
//this.domElement.addEventListener( 'DOMMouseScroll', hid_events.onMouseWheel, false ); // firefox
}

var Texture = {};
Texture.HandleLoadedTexture2D = function (texture, flipY) {
    gl.activeTexture(gl.TEXTURE0 + texture.unit);
    gl.bindTexture(gl.TEXTURE_2D, texture.obj);
    if (flipY != undefined && flipY == true)
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
    if (texture.unit == height_map_unit)
        height_map_size = [texture.image.width, texture.image.height];
    return texture;
}
Texture.LoadTexture2D = function (unit, name) {
    var texture = {};
    texture.obj = gl.createTexture();
    texture.unit = unit;
    texture.image = new Image();
    texture.image.setAttribute('crossorigin', 'anonymous');
    texture.image.onload = function () {
        Texture.HandleLoadedTexture2D(texture, false)
    }
    texture.image.src = name;
    return texture;
}

var ShProg = {
Create: function (shaderList) {
    var shaderObjs = [];
    for (var i_sh = 0; i_sh < shaderList.length; ++i_sh) {
        var shderObj = this.Compile(shaderList[i_sh].source, shaderList[i_sh].stage);
        if (shderObj) shaderObjs.push(shderObj);
    }
    var prog = {}
    prog.progObj = this.Link(shaderObjs)
    if (prog.progObj) {
        prog.attrInx = {};
        var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
        for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
            var name = gl.getActiveAttrib(prog.progObj, i_n).name;
            prog.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
        }
        prog.uniLoc = {};
        var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
        for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
            var name = gl.getActiveUniform(prog.progObj, i_n).name;
            prog.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
        }
    }
    return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: function (source, shaderStage) {
    var shaderScript = document.getElementById(source);
    if (shaderScript)
        source = shaderScript.text;
    var shaderObj = gl.createShader(shaderStage);
    gl.shaderSource(shaderObj, source);
    gl.compileShader(shaderObj);
    var status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
    if (!status) alert(gl.getShaderInfoLog(shaderObj));
    return status ? shaderObj : null;
},
Link: function (shaderObjs) {
    var prog = gl.createProgram();
    for (var i_sh = 0; i_sh < shaderObjs.length; ++i_sh)
        gl.attachShader(prog, shaderObjs[i_sh]);
    gl.linkProgram(prog);
    status = gl.getProgramParameter(prog, gl.LINK_STATUS);
    if ( !status ) alert(gl.getProgramInfoLog(prog));
    return status ? prog : null;
} };

var VertexBuffer = {
Create: function(attribs, indices, type) {
    var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
    for (var i=0; i<attribs.length; ++i) {
        buffer.buf.push(gl.createBuffer());
        buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc, no_of: attribs[i].data.length/attribs[i].attrSize });
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    if ( buffer.inxLen > 0 ) {
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    }
    return buffer;
},
Draw: function(bufObj) {
    for (var i=0; i<bufObj.buf.length; ++i) {
        gl.bindBuffer(gl.ARRAY_BUFFER, bufObj.buf[i]);
        gl.vertexAttribPointer(bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray( bufObj.attr[i].loc);
    }
    if ( bufObj.inxLen > 0 ) {
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufObj.inx);
        gl.drawElements(bufObj.primitive_type, bufObj.inxLen, gl.UNSIGNED_SHORT, 0);
        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
    }
    else
        gl.drawArrays(bufObj.primitive_type, 0, bufObj.attr[0].no_of );
    for (var i=0; i<bufObj.buf.length; ++i)
        gl.disableVertexAttribArray(bufObj.attr[i].loc);
    gl.bindBuffer( gl.ARRAY_BUFFER, null );
} };

initScene();

})();
</script>

</body>